package server;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import server.classloader.WebappClassLoader;
import server.mapping.Connector;
import server.mapping.Context;
import server.mapping.Host;
import server.mapping.Service;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 *
 * 1.解析server.xml, 实例化相关对象以及Servlet
 * 2.自定义ClassLoader加载Servlet, 由于可能类相同类限定名, 所以需要打破双亲委派的ClassLoader
 *   实例化Servlet时需要各自的ClassLoader去实例化
 */
public class Bootstrap {

    // 工作线程池
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            10,
            50,
            100L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(50),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy() //不丢失请求, 直接主线程执行run
    );

    // 模拟多个service服务, key为名字
    //private static Map<String, Service> serviceMap;
    private Service service;

    /**
     * 解析server.xml, 实例化servlet, 构造mapping关系
     * @throws Exception
     */
    public void init() throws Exception {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//Service");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element =  selectNodes.get(i);

                Service.ServiceBuilder serviceBuilder = Service.builder().hostMap(new HashMap<>());

                /**
                 * 1. 获取service  <Service name="Catalina">
                 */
                String serviceName = element.attributeValue("name");
                serviceBuilder.name(serviceName);

                /**
                 * 2.获取Connector
                 */
                Connector.ConnectorBuilder connectorBuilder = Connector.builder();
                Element connectorElement = (Element) element.selectSingleNode("Connector");
                String port = connectorElement.attributeValue("port");
                Connector connector = connectorBuilder.port(Integer.parseInt(port)).build();
                serviceBuilder.connector(connector);

                /**
                 * 3.获取Host (Engine只有一个,故跳过 )
                 */
                Map<String, Host> hostMap = new HashMap<>();
                List<Element> hostNodes = element.selectNodes("Engine/Host");
                hostNodes.forEach(hostNode -> {
                    Host.HostBuilder hostBuilder = Host.builder().contextMap(new HashMap<>());

                    String hostname = hostNode.attributeValue("name");
                    String appBase = hostNode.attributeValue("appBase");
                    hostBuilder
                            .hostname(hostname)
                            .appBase(appBase);

                    /**
                     * 4.获取Context (若没配置Context节点, 则默认扫描其目录, 目录名作为 docBase 和 path)
                     */
                    List<Element> contextNodes = hostNode.selectNodes("Context");
                    Map<String, Context> contextMap = new HashMap<>();
                    if (null == contextNodes || contextNodes.isEmpty()){
                        File dir = new File(appBase);
                        if (dir.exists() && dir.isDirectory()) {
                            File[] files = dir.listFiles();
                            for (File file : files) {
                                Context.ContextBuilder contextBuilder = Context.builder();
                                String path = "/" + file.getName();
                                String path1 = file.getPath();

                                String classLoaderRootDir = appBase + path;
                                WebappClassLoader webappClassLoader = new WebappClassLoader(classLoaderRootDir);

                                Context context = contextBuilder
                                        .docBase(path)
                                        .path(path)
                                        .webappClassLoader(webappClassLoader)
                                        .build();
                                /**
                                 * 加载各自webapp的servlet并实例化
                                 * TODO 要求web.xml位于web应用根目录
                                 */
                                try {
                                    loadServlet(classLoaderRootDir, context, webappClassLoader);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                contextMap.put(path, context);
                            }

                        } else {
                            System.out.println("虚拟主机没有根目录");
                        }
                    } else {
                        contextNodes.forEach(contextNode -> {
                            Context.ContextBuilder contextBuilder = Context.builder();

                            // 若没有配置, 则以appBase为root目录, 扫描子目录作为docBase 和 path
                            String docBase = contextNode.attributeValue("docBase");
                            String path = contextNode.attributeValue("path");
                            String classLoaderRootDir = appBase + path;

                            WebappClassLoader webappClassLoader = new WebappClassLoader(classLoaderRootDir);

                            Context context = contextBuilder
                                    .docBase(path)
                                    .path(path)
                                    .webappClassLoader(webappClassLoader)
                                    .servletMap(new HashMap<>())
                                    .build();
                            /**
                             * 加载各自webapp的servlet并实例化
                             * TODO 要求web.xml位于web应用根目录
                             */
                            try {
                                loadServlet(classLoaderRootDir, context, webappClassLoader);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            contextMap.put(path, context);
                        });
                    }

                    Host host = hostBuilder.contextMap(contextMap).build();

                    hostMap.put(hostname, host);
                });

                this.service = serviceBuilder.hostMap(hostMap).build();
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }



    public void start() throws Exception {

        ServerSocket serverSocket = new ServerSocket(this.service.getConnector().getPort());
        System.out.println("=====>>>Minicat start on port：" + this.service.getConnector().getPort());

        while(true) {
            Socket socket = serverSocket.accept();
            RequestProcessorV1 requestProcessor = new RequestProcessorV1(socket, this.service);
            //requestProcessor.start();
            threadPoolExecutor.execute(requestProcessor);
        }
    }


    /**
     * 加载解析web.xml，初始化Servlet
     */
    private void loadServlet(String baseDir, Context context, WebappClassLoader webappClassLoader) throws IOException {
        File file = new File(baseDir + "/web.xml");
        InputStream resourceAsStream = new FileInputStream(file);
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element =  selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();


                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /lagou
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();

                // 使用findClass, 即可不使用双亲委派
                Class<?> clazz = webappClassLoader.findClass(servletClass);

                context.getServletMap().put(urlPattern, (HttpServlet) clazz.newInstance());
                //servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());

            }

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            resourceAsStream.close();
        }
    }


    /**
     * Minicat 的程序启动入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.init();
            bootstrap.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

